package eu.irreality.dai.world.gen;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;

import eu.irreality.dai.gameplay.world.LevelDescriptor;
import eu.irreality.dai.ui.cell.DisplayableCharacter;
import eu.irreality.dai.ui.cell.DisplayableObject;
import eu.irreality.dai.util.GridUtilities;
import eu.irreality.dai.util.SortedList;
import eu.irreality.dai.world.gen.strategy.SimpleStairGenerationStrategy;
import eu.irreality.dai.world.gen.strategy.StairGenerationStrategy;
import eu.irreality.dai.world.level.CavernousLevel;
import eu.irreality.dai.world.level.Level;

/**
 * Generator of Cavernous Levels based on a Cellular Automaton.
 * 
 */
public class CACavernousLevelGenerator extends CavernousLevelGenerator
{
    // % of walls on initialization
    private final static int P = 45;

    // # of generations
    private final static int GENERATIONS = 5;

    private final static int WALL_THRESHOLD = 5;

    private final static int GROUND_THRESHOLD = 3;

    private DisplayableObject[][] level;

    private DisplayableObject[][] level2;

    private int rows = 25;
    private int cols = 80;

    public void setRows(int rows)
    {
	this.rows = rows;
    }

    public void setCols(int cols)
    {
	this.cols = cols;
    }

    public Level generate(LevelDescriptor ld)
    {
	Level l = generate(rows, cols);
	l.setDescriptor(ld);
	sgs.addStairs(l);
	return l;
    }

    private StairGenerationStrategy sgs = new SimpleStairGenerationStrategy(); // by

    // default

    public void setStairGenerationStrategy(StairGenerationStrategy sgs)
    {
	this.sgs = sgs;
    }

    /**
     * Generates a CavernousLevel using a Cellular Automaton technique.
     */
    @SuppressWarnings("unchecked")
    public Level generate(int rows, int cols)
    {
	level = new DisplayableObject[rows][cols];
	level2 = new DisplayableObject[rows][cols];
	randomizeCavernousLevel(P);
	for (int k = 1; k <= GENERATIONS; k++)
	{
	    nextGeneration(WALL_THRESHOLD, GROUND_THRESHOLD);
	}
	eliminateIsolatedChunks();
	CavernousLevel l = new CavernousLevel(rows, cols);
	SortedList[][] levelGrid = GridUtilities.simpleToComplexGrid(level);
	l.setGrid(levelGrid);
	return l;
    }

    private void randomizeCavernousLevel(int p)
    {
	for (int i = 0; i < level.length; i++)
	{
	    Arrays.fill(level[i], WALL);
	    Arrays.fill(level2[i], WALL);
	}
	for (int i = 1; i < level.length - 1; i++)
	{
	    for (int j = 1; j < level[0].length - 1; j++)
	    {
		DisplayableObject c;
		int r = rand.nextInt(100);
		if (r < p)
		{
		    c = WALL;
		} else
		{
		    c = GROUND;
		}
		level[i][j] = c;
	    }
	}
    }

    /**
     * The rules of the Cellular Automaton are described by wallThreshold and
     * groundTreshold. Given a cell in an instant, and considering 8 neighbors,
     * if the cell has > wallThreshold wall neighbors, the cell becomes (or
     * remains) wall. If the cell has <= groundThreshold wall neighbors, the
     * cell becomes (or remains) ground. If the cell neither has > wallThreshold
     * neither <= groundTreshold neighbors, the cell remains.
     * 
     * @param wallThreshold
     *            Minimum of wall neighbors needed to become wall.
     * @param groundThreshold
     *            Maximum of wall neighbors needed to become ground.
     */
    private void nextGeneration(int wallThreshold, int groundThreshold)
    {
	for (int i = 1; i < level.length - 1; i++)
	{
	    for (int j = 1; j < level[0].length - 1; j++)
	    {

		DisplayableObject c = null;

		int neig = countWallNeighbors(level, i, j);
		// int neig2 = countWallNeighbors2(level, i, j);

		if (neig > wallThreshold)
		{
		    c = WALL;
		} else if (neig <= groundThreshold)
		{
		    c = GROUND;
		}

		if (c != null)
		{
		    level2[i][j] = c;
		}
	    }
	}
	for (int i = 1; i < level.length - 1; i++)
	{
	    for (int j = 1; j < level[0].length - 1; j++)
	    {
		level[i][j] = level2[i][j];
	    }
	}

    }

    private int countWallNeighbors(DisplayableObject[][] grid, int x, int y)
    {

	int counter = 0;

	for (int ii = -1; ii <= 1; ii++)
	{
	    for (int jj = -1; jj <= 1; jj++)
	    {
		int xx = x + ii;
		int yy = y + jj;
		if (xx > 0 && yy > 0 && xx < grid.length - 1
			&& yy < grid[0].length - 1)
		{
		    if (grid[xx][yy].equals(WALL))
		    {
			counter++;
		    }
		}
	    }
	}
	if (grid[x][y].equals(WALL))
	{
	    counter--;
	}
	return counter;
    }

    private int countWallNeighbors2(DisplayableObject[][] grid, int x, int y)
    {
	int counter = 0;

	for (int ii = x - 2; ii <= x + 2; ii++)
	{
	    for (int jj = y - 2; jj <= y + 2; jj++)
	    {
		if (Math.abs(ii - x) == 2 && Math.abs(jj - y) == 2)
		    continue;
		if (ii < 0 || jj < 0 || ii >= grid.length
			|| jj >= grid[0].length)
		    continue;
		if (grid[ii][jj].equals(WALL))
		    counter++;
	    }
	}
	return counter;
    }

    /**
     * TODO: This algorithm needs to be improved. Sometimes there exist two big
     * chunks and the level sux.
     */
    private void eliminateIsolatedChunks()
    {
	char labelChar = '1';
	ArrayList<DisplayableObject> labels = new ArrayList<DisplayableObject>();
	ArrayList<Integer> counters = new ArrayList<Integer>();
	int maxCounter = Integer.MIN_VALUE;
	int maxPos = -1;

	for (int i = 0; i < level.length; i++)
	{
	    for (int j = 0; j < level[0].length; j++)
	    {
		level2[i][j] = level[i][j];
	    }
	}
	for (int i = 0; i < level.length; i++)
	{
	    for (int j = 0; j < level[0].length; j++)
	    {
		if (level2[i][j].equals(GROUND))
		{
		    DisplayableObject label = new DisplayableCharacter(
			    labelChar, Color.WHITE);
		    labels.add(label);
		    int counter = connectedComponentLabeling(i, j, label);
		    counters.add(counter);
		    if (counter > maxCounter)
		    {
			maxCounter = counter;
			maxPos = labels.indexOf(label);
		    }
		    labelChar++;
		}
	    }
	}
	for (int i = 0; i < level.length; i++)
	{
	    for (int j = 0; j < level[0].length; j++)
	    {
		if (level[i][j].equals(GROUND)
			&& !level2[i][j].equals(labels.get(maxPos)))
		{
		    level[i][j] = WALL;
		}
	    }
	}
    }

    private int connectedComponentLabeling(int i, int j, DisplayableObject label)
    {
	int counter = 0;
	if (i > 0 && j > 0 && i < level2.length && j < level2[0].length
		&& level2[i][j].equals(GROUND))
	{
	    level2[i][j] = label;
	    counter++;
	    counter += connectedComponentLabeling(i - 1, j - 1, label);
	    counter += connectedComponentLabeling(i - 1, j, label);
	    counter += connectedComponentLabeling(i - 1, j + 1, label);
	    counter += connectedComponentLabeling(i, j - 1, label);
	    counter += connectedComponentLabeling(i, j + 1, label);
	    counter += connectedComponentLabeling(i + 1, j - 1, label);
	    counter += connectedComponentLabeling(i + 1, j, label);
	    counter += connectedComponentLabeling(i + 1, j + 1, label);
	}
	return counter;
    }

}
